// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

#region

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;

#endregion

namespace Neto.Encry;

public class SM2
{
    private const int RS_LEN = 32;
    private static readonly X9ECParameters x9 = GMNamedCurves.GetByName("SM2P256V1");

    #region ASN.1 DER编码格式转换

    /// <summary>
    ///     将ASN.1 DER编码格式的签名转换为固定长度的字节数组。
    /// </summary>
    /// <param name="signDer">ASN.1 DER编码格式的签名。</param>
    /// <returns>固定长度的字节数组。</returns>
    /// <exception cref="ArgumentException">如果输入的签名格式不正确。</exception>
    public static byte[] RsAsn1ToPlainByteArray(byte[] signDer)
    {
        // 获取ASN.1序列
        var seq = Asn1Sequence.GetInstance(signDer);
        // 获取r和s的字节数组
        var r = BigIntToFixedLengthBytes(DerInteger.GetInstance(seq[0]).Value);
        var s = BigIntToFixedLengthBytes(DerInteger.GetInstance(seq[1]).Value);
        // 创建结果数组
        var result = new byte[RS_LEN * 2];
        // 复制r和s到结果数组
        Buffer.BlockCopy(r, 0, result, 0, r.Length);
        Buffer.BlockCopy(s, 0, result, RS_LEN, s.Length);
        return result;
    }

    /// <summary>
    ///     将固定长度的字节数组转换为ASN.1 DER编码格式的签名。
    /// </summary>
    /// <param name="sign">固定长度的字节数组。</param>
    /// <returns>ASN.1 DER编码格式的签名。</returns>
    /// <exception cref="ArgumentException">如果输入的字节数组长度不正确。</exception>
    public static byte[] RsAsn1FromPlainByteArray(byte[] sign)
    {
        if (sign.Length != RS_LEN * 2) throw new ArgumentException("Invalid RS length.", nameof(sign));
        // 获取r和s的BigInteger值
        BigInteger r = new(1, Arrays.CopyOfRange(sign, 0, RS_LEN));
        BigInteger s = new(1, Arrays.CopyOfRange(sign, RS_LEN, RS_LEN * 2));
        // 创建ASN.1编码向量
        Asn1EncodableVector v = [new DerInteger(r), new DerInteger(s)];
        try
        {
            // 返回DER编码的字节数组
            return new DerSequence(v).GetEncoded("DER");
        }
        catch (IOException e)
        {
            throw new IOException("Failed to encode ASN.1 sequence.", e);
        }
    }

    /// <summary>
    ///     将BigInteger值转换为固定长度的字节数组。
    /// </summary>
    /// <param name="rOrS">BigInteger值。</param>
    /// <returns>固定长度的字节数组。</returns>
    /// <exception cref="ArgumentException">如果BigInteger值长度不正确。</exception>
    private static byte[] BigIntToFixedLengthBytes(BigInteger rOrS)
    {
        var rs = rOrS.ToByteArray();
        if (rs.Length == RS_LEN) return rs;
        if (rs.Length == RS_LEN + 1 && rs[0] == 0) return Arrays.CopyOfRange(rs, 1, RS_LEN + 1);
        if (rs.Length < RS_LEN)
        {
            var result = new byte[RS_LEN];
            Arrays.Fill(result, 0);
            Buffer.BlockCopy(rs, 0, result, RS_LEN - rs.Length, rs.Length);
            return result;
        }

        throw new ArgumentException("Invalid RS length: " + Hex.ToHexString(rs));
    }

    #endregion

    #region 密钥生成

    /// <summary>
    ///     生成SM2的公钥和私钥。
    /// </summary>
    /// <returns>包含公钥和私钥的元组。</returns>
    public static (byte[] PublicKey, byte[] PrivateKey) GenerateKeyPairToBytes()
    {
        ECKeyPairGenerator eCKeyPairGenerator = new();
        eCKeyPairGenerator.Init(new ECKeyGenerationParameters(new ECDomainParameters(x9), new SecureRandom()));
        var asymmetricCipherKeyPair = eCKeyPairGenerator.GenerateKeyPair();
        var publicKey = ((ECPublicKeyParameters)asymmetricCipherKeyPair.Public).Q.GetEncoded(false);
        var privateKey = ((ECPrivateKeyParameters)asymmetricCipherKeyPair.Private).D.ToByteArray();
        return (publicKey, privateKey);
    }

    /// <summary>
    ///     生成SM2的公钥和私钥。
    /// </summary>
    /// <returns>包含公钥和私钥的元组。</returns>
    public static (string PublicKey, string PrivateKey) GenerateKeyPair()
    {
        var (publicKey, privateKey) = GenerateKeyPairToBytes();
        return (Convert.ToBase64String(publicKey), Convert.ToBase64String(privateKey));
    }

    #endregion

    #region 签名和验证

    /// <summary>
    ///     使用SM2算法对数据进行签名。
    /// </summary>
    /// <param name="data">要签名的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名的字节数组（非ASN.1 DER编码格式）</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static byte[] Sign(byte[] data, byte[] privateKey, byte[]? userId = null)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));

        SM2Signer sM2Signer = new(new SM3Digest());
        ICipherParameters parameters =
            new ParametersWithRandom(new ECPrivateKeyParameters(new BigInteger(1, privateKey),
                new ECDomainParameters(x9)));
        if (userId != null) parameters = new ParametersWithID(parameters, userId);

        sM2Signer.Init(true, parameters);
        sM2Signer.BlockUpdate(data, 0, data.Length);

        var dataSignedDer = sM2Signer.GenerateSignature();
        var dataSigned = RsAsn1ToPlainByteArray(dataSignedDer);
        return dataSigned;
    }

    /// <summary>
    ///     使用SM2算法对数据进行签名。
    /// </summary>
    /// <param name="data">要签名的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名的字节数组（非ASN.1 DER编码格式）</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static string Sign(string data, string privateKey, string userId = "")
    {
        var dataSigned = Sign(data.ToByteArray(), privateKey.ToByteArray(), userId.ToByteArray());
        return Convert.ToBase64String(dataSigned);
    }

    /// <summary>
    ///     验证使用SM2算法生成的签名。
    /// </summary>
    /// <param name="data">原始数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="dataSigned">签名的字节数组。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名是否有效的布尔值。</returns>
    /// <exception cref="ArgumentNullException">如果数据、公钥或签名为空。</exception>
    public static bool VerifySign(byte[] data, byte[] publicKey, byte[] dataSigned, byte[]? userId = null)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
        if (dataSigned == null) throw new ArgumentNullException(nameof(dataSigned));

        var dataSignedDer = RsAsn1FromPlainByteArray(dataSigned);

        SM2Signer sM2Signer = new(new SM3Digest());
        ICipherParameters parameters =
            new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey), new ECDomainParameters(x9));
        if (userId != null) parameters = new ParametersWithID(parameters, userId);

        sM2Signer.Init(false, parameters);
        sM2Signer.BlockUpdate(data, 0, data.Length);
        return sM2Signer.VerifySignature(dataSignedDer);
    }

    /// <summary>
    ///     验证使用SM2算法生成的签名。
    /// </summary>
    /// <param name="data">原始数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="dataSigned">签名的字节数组。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名是否有效的布尔值。</returns>
    /// <exception cref="ArgumentNullException">如果数据、公钥或签名为空。</exception>
    public static bool VerifySign(string data, string publicKey, string dataSigned, string userId = "")
    {
        return VerifySign(data.ToByteArray(), publicKey.ToByteArray(), dataSigned.ToByteArray(), userId.ToByteArray());
    }

    /// <summary>
    ///     使用SM2算法对数据哈希进行签名。
    /// </summary>
    /// <param name="data">要签名的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static byte[] SignByDataHash(byte[] data, byte[] privateKey, byte[]? userId = null)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));

        var dataHash = SM3.Hash(data);

        SM2Signer sM2Signer = new(new SM3Digest());
        ICipherParameters parameters =
            new ParametersWithRandom(new ECPrivateKeyParameters(new BigInteger(1, privateKey),
                new ECDomainParameters(x9)));
        if (userId != null) parameters = new ParametersWithID(parameters, userId);

        sM2Signer.Init(true, parameters);
        sM2Signer.BlockUpdate(dataHash, 0, dataHash.Length);

        var dataSignedDer = sM2Signer.GenerateSignature();
        var dataSigned = RsAsn1ToPlainByteArray(dataSignedDer);
        return dataSigned;
    }

    /// <summary>
    ///     使用SM2算法对数据哈希进行签名。
    /// </summary>
    /// <param name="data">要签名的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static string SignByDataHash(string data, string privateKey, string userId = "")
    {
        var dataSigned = SignByDataHash(data.ToByteArray(), privateKey.ToByteArray(), userId.ToByteArray());
        return Convert.ToBase64String(dataSigned);
    }

    /// <summary>
    ///     验证使用SM2算法对数据哈希生成的签名。
    /// </summary>
    /// <param name="data">原始数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="dataSigned">签名的字节数组。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名是否有效的布尔值。</returns>
    /// <exception cref="ArgumentNullException">如果数据、公钥或签名为空。</exception>
    public static bool VerifySignByDataHash(byte[] data, byte[] publicKey, byte[] dataSigned, byte[]? userId = null)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
        if (dataSigned == null) throw new ArgumentNullException(nameof(dataSigned));

        var dataSignedDer = RsAsn1FromPlainByteArray(dataSigned);

        var dataHash = SM3.Hash(data);

        SM2Signer sM2Signer = new(new SM3Digest());
        ICipherParameters parameters =
            new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey), new ECDomainParameters(x9));
        if (userId != null) parameters = new ParametersWithID(parameters, userId);

        sM2Signer.Init(false, parameters);
        sM2Signer.BlockUpdate(dataHash, 0, dataHash.Length);
        return sM2Signer.VerifySignature(dataSignedDer);
    }

    /// <summary>
    ///     验证使用SM2算法对数据哈希生成的签名。
    /// </summary>
    /// <param name="data">原始数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="dataSigned">签名的字节数组。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <returns>签名是否有效的布尔值。</returns>
    /// <exception cref="ArgumentNullException">如果数据、公钥或签名为空。</exception>
    public static bool VerifySignByDataHash(string data, string publicKey, string dataSigned, string userId = "")
    {
        return VerifySignByDataHash(data.ToByteArray(), publicKey.ToByteArray(), dataSigned.ToByteArray(),
            userId.ToByteArray());
    }

    #endregion

    #region 加密和解密

    /// <summary>
    ///     使用SM2算法对数据进行加密。
    /// </summary>
    /// <param name="data">要加密的数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <param name="mode">加密模式（默认为C1C3C2）。</param>
    /// <returns>加密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或公钥为空。</exception>
    public static byte[] Encrypt(byte[] data, byte[] publicKey, byte[]? userId = null,
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));

        SM2Engine sM2Engine = new(new SM3Digest(), SM2Engine.Mode.C1C3C2);
        ICipherParameters cipherParameters =
            new ParametersWithRandom(new ECPublicKeyParameters(x9.Curve.DecodePoint(publicKey),
                new ECDomainParameters(x9)));
        if (userId != null) cipherParameters = new ParametersWithID(cipherParameters, userId);

        sM2Engine.Init(true, cipherParameters);
        data = sM2Engine.ProcessBlock(data, 0, data.Length);
        if (mode == SM2Engine.Mode.C1C2C3) data = C132ToC123(data);

        return data;
    }

    /// <summary>
    ///     使用SM2算法对数据进行加密。
    /// </summary>
    /// <param name="data">要加密的数据。</param>
    /// <param name="publicKey">公钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <param name="mode">加密模式（默认为C1C3C2）。</param>
    /// <returns>加密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或公钥为空。</exception>
    public static string Encrypt(string data, string publicKey, string userId = "",
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
    {
        var dataEncrypted = Encrypt(data.ToByteArray(), publicKey.ToByteArray(), userId.ToByteArray(), mode);
        return Convert.ToBase64String(dataEncrypted);
    }

    /// <summary>
    ///     使用SM2算法对数据进行解密。
    /// </summary>
    /// <param name="dataEncrypted">要解密的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <param name="mode">加密模式（默认为C1C3C2）。</param>
    /// <returns>解密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static byte[] Decrypt(byte[] dataEncrypted, byte[] privateKey, byte[]? userId = null,
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
    {
        if (dataEncrypted == null) throw new ArgumentNullException(nameof(dataEncrypted));
        if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));

        if (mode == SM2Engine.Mode.C1C2C3) dataEncrypted = C123ToC132(dataEncrypted);

        SM2Engine sM2Engine = new(new SM3Digest(), SM2Engine.Mode.C1C3C2);
        ICipherParameters cipherParameters =
            new ECPrivateKeyParameters(new BigInteger(1, privateKey), new ECDomainParameters(x9));
        if (userId != null) cipherParameters = new ParametersWithID(cipherParameters, userId);

        sM2Engine.Init(false, cipherParameters);
        return sM2Engine.ProcessBlock(dataEncrypted, 0, dataEncrypted.Length);
    }

    /// <summary>
    ///     使用SM2算法对数据进行解密。
    /// </summary>
    /// <param name="dataEncrypted">要解密的数据。</param>
    /// <param name="privateKey">私钥。</param>
    /// <param name="userId">用户ID（可选）。</param>
    /// <param name="mode">加密模式（默认为C1C3C2）。</param>
    /// <returns>解密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据或私钥为空。</exception>
    public static string Decrypt(string data, string privateKey, string userId = "",
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2)
    {
        var dataDecrypted = Decrypt(data.ToByteArray(), privateKey.ToByteArray(), userId.ToByteArray(), mode);
        return Convert.ToBase64String(dataDecrypted);
    }

    /// <summary>
    ///     将C1C2C3格式的数据转换为C1C3C2格式。
    /// </summary>
    /// <param name="c1c2c3">C1C2C3格式的数据。</param>
    /// <returns>C1C3C2格式的数据。</returns>
    private static byte[] C123ToC132(byte[] c1c2c3)
    {
        var num = (((x9.Curve.FieldSize + 7) >> 3) << 1) + 1;
        var array = new byte[c1c2c3.Length];
        Array.Copy(c1c2c3, 0, array, 0, num);
        Array.Copy(c1c2c3, c1c2c3.Length - 32, array, num, 32);
        Array.Copy(c1c2c3, num, array, num + 32, c1c2c3.Length - num - 32);
        return array;
    }

    /// <summary>
    ///     将C1C3C2格式的数据转换为C1C2C3格式。
    /// </summary>
    /// <param name="c1c3c2">C1C3C2格式的数据。</param>
    /// <returns>C1C2C3格式的数据。</returns>
    private static byte[] C132ToC123(byte[] c1c3c2)
    {
        var num = (((x9.Curve.FieldSize + 7) >> 3) << 1) + 1;
        var array = new byte[c1c3c2.Length];
        Array.Copy(c1c3c2, 0, array, 0, num);
        Array.Copy(c1c3c2, num + 32, array, num, c1c3c2.Length - num - 32);
        Array.Copy(c1c3c2, num, array, c1c3c2.Length - 32, 32);
        return array;
    }

    #endregion
}